﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Microsoft.Goldenlight.Resources;

namespace Microsoft.Goldenlight.Framework
{
    /// <summary>
    /// 	Central class for game engine. GameObject's should be registered with ObjectManager before use and unregistered after they are used.
    /// </summary>
    public abstract class GameObject : RegisterableObject
    {
        #region Overrides of LifetimeObject

        #region Implementation of ICloneable

        /// <summary>
        /// 	Called by Clone. Objects need to implement this method to copy all public properties not marked with TorqueCloneIgnore attribute.
        /// </summary>
        /// <param name="newLifetimeObject"> The object to be copied over. </param>
        public override void CopyTo(LifetimeObject newLifetimeObject)
        {
            base.CopyTo(newLifetimeObject);
            var newGameObject = newLifetimeObject as GameObject;
            if (newGameObject != null)
            {
                foreach (var behavior in _behaviors.Values.Select(behavior => behavior.Clone()).OfType<Behavior>())
                {
                    newGameObject.AddBehavior(behavior);
                }
            }
        }

        #endregion

        #region Implementation of IDisposable

        /// <summary>
        /// 	Releases all resources used by the Game class.
        /// </summary>
        /// <param name="disposing"> true to release both managed and unmanaged resources; false to release only unmanaged resources. </param>
        protected override void Dispose(bool disposing)
        {
            _isDisposed = true;
            if (disposing)
            {
                ClearBehaviors(true);
                _group = Group.None;
                if (_parentContainer != null)
                {
                    _parentContainer.RemoveObject(this, false);
                }
            }
            base.Dispose(disposing);
        }

        #endregion

        #endregion

        #region Parent Container Operations

        #region Fields & Properties

        private Container _parentContainer;

        /// <summary>
        /// 	Parent container which this object is in.
        /// </summary>
        /// <remarks>
        /// 	Objects can be moved between containers by setting this property.
        /// </remarks>
        [CloneIgnore]
        public Container ParentContainer
        {
            get
            {
                return _parentContainer;
            }
        }

        #endregion

        #region Add & Remove

        /// <summary>
        /// 	Called after the object is attached to a parent container.
        /// </summary>
        /// <remarks>
        /// 	Override this to hook up functionality to the parent container.
        /// </remarks>
        protected virtual void OnAddToParentContainer()
        {
        }

        /// <summary>
        /// 	Called when the object is being removed from its parent container, but before it has actually occurred.
        /// </summary>
        /// <remarks>
        /// 	Override this to unhook functionality from the parent container.
        /// </remarks>
        protected virtual void OnRemoveFromParentContainer()
        {
        }

        /// <summary>
        /// 	Adds to the specified parent container.
        /// </summary>
        /// <param name="container"> The parent container to add to. </param>
        internal void AddToParentContainer(Container container)
        {
            if (container != null && _parentContainer == null)
            {
                try
                {
                    _parentContainer = container;
                    OnAddToParentContainer();
                }
                catch (Exception ex)
                {
                    //TODO: Trace
                    Debug.WriteLine(ex.Message);
                }
            }
        }

        /// <summary>
        /// 	Removes From this instance from its parent container.
        /// </summary>
        internal void RemoveFromParentContainer()
        {
            if (_parentContainer != null)
            {
                try
                {
                    OnRemoveFromParentContainer();
                    _parentContainer = null;
                }
                catch (Exception ex)
                {
                    //TODO: Trace
                    Debug.WriteLine(ex.Message);
                }
            }
        }

        #endregion

        #endregion

        #region Behavior Operations

        #region Collection Fields & Properties

        private readonly Dictionary<string, Behavior> _behaviors = new Dictionary<string, Behavior>();

        /// <summary>
        /// 	Sub-behaviors in the current behavior.
        /// </summary>
        public IEnumerable<Behavior> Behaviors
        {
            get
            {
                return _behaviors.Values;
            }
        }

        /// <summary>
        /// 	Return the number of behaviors in the container (top level only).
        /// </summary>
        public int BehaviorsCount
        {
            get
            {
                return _behaviors.Values.Count;
            }
        }

        #endregion

        #region Add & Remove & Clear

        /// <summary>
        /// 	Add a behavior to the game object.
        /// </summary>
        /// <param name="behavior"> Behavior to add. </param>
        /// <returns> True if add successfully. </returns>
        public bool AddBehavior(Behavior behavior)
        {
            if (behavior == null)
            {
                throw new ArgumentNullException("behavior");
            }
            if (string.IsNullOrEmpty(behavior.Name) || behavior.IsDisposed || behavior.OwnerObject != null)
            {
                return false;
            }
            if (!_behaviors.ContainsKey(behavior.Name))
            {
                behavior.Attach(this);
                behavior.NameChanging += HandleBehaviorNameChanging;
                _behaviors.Add(behavior.Name, behavior);
                return true;
            }
            return false;
        }

        /// <summary>
        /// 	Remove a behavior from the game object.
        /// </summary>
        /// <param name="behavior"> Behavior to remove. </param>
        /// <returns> True if remove successfully. </returns>
        public bool RemoveBehavior(Behavior behavior)
        {
            return RemoveBehavior(behavior, false);
        }

        /// <summary>
        /// 	Remove a behavior from the game object.
        /// </summary>
        /// <param name="behavior"> Behavior to remove. </param>
        /// <param name="dispose"> Dispose an object will perform any cleanup that is needed and will clear. </param>
        /// <returns> True if remove successfully. </returns>
        public bool RemoveBehavior(Behavior behavior, bool dispose)
        {
            if (behavior == null)
            {
                throw new ArgumentNullException("behavior");
            }
            if (string.IsNullOrEmpty(behavior.Name) || behavior.OwnerObject != this)
            {
                return false;
            }
            Behavior current;
            if (_behaviors.TryGetValue(behavior.Name, out current) && behavior == current && _behaviors.Remove(behavior.Name))
            {
                behavior.NameChanging -= HandleBehaviorNameChanging;
                behavior.Detach();
                if (dispose)
                {
                    behavior.Dispose();
                }
                return true;
            }
            return false;
        }

        /// <summary>
        /// 	Remove all behaviors from the game object.
        /// </summary>
        /// <param name="dispose"> Dispose an object will perform any cleanup that is needed and will clear. </param>
        public void ClearBehaviors(bool dispose)
        {
            var behaviors = _behaviors.Values.ToArray();
            foreach (var behavior in behaviors)
            {
                behavior.NameChanging -= HandleBehaviorNameChanging;
                behavior.Detach();
                if (dispose)
                {
                    behavior.Dispose();
                }
            }
            _behaviors.Clear();
        }

        #region Event Handlers

        private void HandleBehaviorNameChanging(object sender, PropertyChangeEventArgs<string> args)
        {
            if (!(sender is Behavior))
            {
                throw new ArgumentException(Resx.SenderIsNotABehavior, "sender");
            }
            if (_behaviors.ContainsKey(args.NewValue))
            {
                throw new InvalidOperationException(Resx.DuplicatedBehaviorNameInContainer);
            }
            if (!string.IsNullOrEmpty(args.OldValue))
            {
                _behaviors.Remove(args.OldValue);
            }
            _behaviors[args.NewValue] = (Behavior) sender;
        }

        #endregion

        #endregion

        #region Find

        #region Find Single Behavior

        /// <summary>
        /// 	Searches for a Behavior in the behavior.
        /// </summary>
        /// <remarks>
        /// 	This method can find item which are not Behavior. To find Behavior only use the templated version.
        /// </remarks>
        /// <param name="name"> Name of the behavior to look up. </param>
        /// <returns> A Behavior in the behavior. </returns>
        public Behavior FindBehavior(string name)
        {
            if (string.IsNullOrEmpty(name))
            {
                throw new ArgumentNullException("name");
            }
            Behavior foundBehavior;
            return _behaviors.TryGetValue(name, out foundBehavior) ? foundBehavior : null;
        }

        /// <summary>
        /// 	Searches for an behavior of type T.
        /// </summary>
        /// <typeparam name="T"> The type of the Behavior. </typeparam>
        /// <returns> A behavior of the specified type in the behavior. </returns>
        public T FindBehavior<T>() where T : Behavior
        {
            return _behaviors.Values.OfType<T>().FirstOrDefault();
        }

        /// <summary>
        /// 	Searches for an behavior of type T.
        /// </summary>
        /// <typeparam name="T"> The type of the Behavior. </typeparam>
        /// <param name="name"> Name of the behavior to look up. </param>
        /// <returns> A behavior of the specified type in the behavior. </returns>
        public T FindBehavior<T>(string name) where T : Behavior
        {
            if (string.IsNullOrEmpty(name))
            {
                throw new ArgumentNullException("name");
            }
            Behavior foundBehavior;
            return _behaviors.TryGetValue(name, out foundBehavior) ? foundBehavior as T : null;
        }

        #endregion

        #region Find Behaviors

        /// <summary>
        /// 	Searches for and populates the results list with behaviors of the specified type.
        /// </summary>
        /// <typeparam name="T"> The type of the Behavior. </typeparam>
        /// <returns> A list of behaviors of the specified type. </returns>
        public IEnumerable<T> FindBehaviors<T>() where T : Behavior
        {
            return _behaviors.Values.OfType<T>();
        }

        /// <summary>
        /// 	Searches for and populates the results list with behaviors of the specified type.
        /// </summary>
        /// <typeparam name="T"> The type of the Behavior. </typeparam>
        /// <param name="predicate"> Restricts the range of returned behaviors by specified clause. </param>
        /// <returns> A list of behaviors of the specified type. </returns>
        public IEnumerable<T> FindBehaviors<T>(Func<T, bool> predicate) where T : Behavior
        {
            return _behaviors.Values.OfType<T>().Where(predicate);
        }

        /// <summary>
        /// 	Searches for and populates the results list with behaviors of the specified type.
        /// </summary>
        /// <typeparam name="T"> The type of the Behavior. </typeparam>
        /// <param name="results"> A list of behaviors of the specified type. </param>
        public void FindBehaviors<T>(ref List<T> results) where T : Behavior
        {
            if (results == null)
            {
                throw new ArgumentNullException("results");
            }
            results.AddRange(FindBehaviors<T>());
        }

        /// <summary>
        /// 	Searches for and populates the results list with behaviors of the specified type.
        /// </summary>
        /// <typeparam name="T"> The type of the Behavior. </typeparam>
        /// <param name="predicate"> Restricts the range of returned behaviors by specified clause. </param>
        /// <param name="results"> A list of behaviors of the specified type. </param>
        public void FindBehaviors<T>(ref List<T> results, Func<T, bool> predicate) where T : Behavior
        {
            if (results == null)
            {
                throw new ArgumentNullException("results");
            }
            results.AddRange(FindBehaviors(predicate));
        }

        #endregion

        #endregion

        #endregion

        #region Group Operations

        #region Fields & Properties

        private Group _group = Group.None;

        /// <summary>
        /// 	The Group for this object.
        /// </summary>
        public Group Group
        {
            get
            {
                return _group;
            }
            set
            {
                _group = value;
            }
        }

        #endregion

        /// <summary>
        /// 	Test whether an object is of a particular group.
        /// </summary>
        /// <param name="group"> The Group to test against. </param>
        /// <returns> If the passed group includes multiple groups, the test returns true if any of our groups matches any of the passed groups. </returns>
        public bool OfGroup(Group group)
        {
            return (_group & group) != Group.None;
        }

        /// <summary>
        /// 	Add (or subtrct) group on a GameObject.
        /// </summary>
        /// <param name="group"> Group to set. </param>
        /// <param name="add"> True to add, false to substract. </param>
        public void SetGroup(Group group, bool add)
        {
            if (add)
            {
                _group += group;
            }
            else
            {
                _group -= group;
            }
        }

        #endregion
    }
}